/** @file   xmlFile.h
 *  @brief  Contains the class XMLFile.
 *
 *  XMLFile is an easy to use (i.e. limited) merger of both the
 *  memshell and XMLparser made by Jurjen Caarls. This class can
 *  be very useful to read and write settings-files.
 *
 *  @author A. M. van Driel
 *  @date   November 2003
 */

#ifndef _XML_FILE_H
#define _XML_FILE_H

#include <cstdio>
#include <unistd.h>
#include <fstream>
#include <string>
#include <sys/stat.h>
#include <sys/file.h>

#include "memfs.h"

/** @class  XMLFile
 *  @brief  A fast-and-easy interface to an XML (settings) file.
 *
 *  This class can open XML-files to read from or write to.
 *  This class functions as a simple, somewhat limited wrapper of
 *  J. Caarls Mshell, Mfs and XMLParser-classes. 
 *
 *  @author A. M. van Driel
 *  @date   September 2003
 */
class XMLFile
{   public:
    /// Constructor, works similar as OpenFile.
    XMLFile(const std::string &file="", const char mode='R'): lockfile(NULL), fileOpen(false)
    {
        memShell = new Mshell( memFS );
        OpenFile(file, "/", mode, true);
    }
    /// Constructor, works similar as OpenFile. Default: read-mode.
    XMLFile(const std::string &file, const std::string &dir, 
            const char mode='R', const bool readOnOpen=true ): lockfile(NULL), fileOpen(false)
    {
        memShell = new Mshell( memFS );
        OpenFile( file, dir, mode, readOnOpen );
    }

    /// Closes a file. If (writeOnClose && write-mode) data is written.
    ~XMLFile(void)
    {   UnlockFile();
        CloseFile( false );
        delete memShell;
    }

    /// Opens an XML-file for reading or writing.
    int OpenFile( const std::string &file, const std::string &dir, 
            const char mode='R', const bool readOnOpen=true );
    /// Opens an XML-file. No need to start in a certain dir.
    int OpenFile( const std::string &file, const char mode='R', 
            const bool readOnOpen=true )
    {   return OpenFile( file, (std::string)"/", mode, readOnOpen );
    }
    /// Closes an XML-file, possibly writing new content in the process.
    int CloseFile( bool writeOnClose=false );
    
    /// Start reading from file (in case of read-mode).
    int Read( void );
    /// Start writing to file (in case of write-mode).
    int Write( void );

    /// Lock file. Wait max 1 sec for it.
    int LockFile( void );

    /// UnLock file.
    int UnlockFile( void );
    
    /// Clears the contents of the memory file system.
    int Clear( void );


    /// Makes a new directory.
    int MkDir( const std::string &dir );
    /// Goes to a certain directory.
    int ChDir( const std::string &dir );
    /// Remove a directory AND ALL ITS CONTENTS!
    int RmDir( const std::string &dir );
    /// Makes a directory empty.
    int EmptyDir( const std::string &dir );
    
    /// Sets the content for an entry-name in the current directory.
    template <class TContent>
    int SetContent(const std::string &entryName, const TContent &value);
    /// Identical, now for non-C type strings.
    int SetContent(const std::string &entryName, const std::string &value);

    template <class TContent>
    int GetContent(const std::string &entryName, TContent &content);
    //  Identical, but now for the (non-C type) string.
    int GetContent(const std::string &entryName, std::string &content);
        
    inline Mfs& GetMfs()
    {
      return memFS;
    }

    private:
    int GetMode( const char &mode );

    FILE *lockfile;
    bool fileOpen;
    bool writeMode;
    bool readMode;
    std::string filename;
    Mfs memFS;
    Mshell* memShell;
};


/**  
 *  Closes current open file in case it was still open.
 *  In case of succes a memShell is opened and one can
 *  start reading and writing.
 * 
 *  @param  file    Filename: file to open.
 *  @param  dir     Directory in XML-file to start in.
 *  @param  mode    Read or write-mode? 
 *                  'r' or 'R' opens the file for reading,
 *                  'w' or 'W' opens the file for writing.
 *  @param  readOnOpen  In case of read-mode, read immediately from file.
 * 
 *  @return Code: errors ( <0 ) or current status of file( >=0 )
 *  
 *  @retval 7   File opened in write-mode already exists, 
 *              possibly overwriting.
 *  @retval 0   No file opened.
 *  @retval -1  The file was already open.
 *  @retval -2  Another file was still open.
 *  @retval -7  You're trying to read from a non-existing file.
 *  @retval -10 No file opened because no filename was passed.     
 */    
inline int XMLFile::OpenFile( const std::string &file, const std::string &dir, 
        const char mode, const bool readOnOpen )
{   int retVal(0);
    if( fileOpen )
    {   if( file!=filename )
        {   CloseFile();
            retVal = -2;
        }
        else
        {   retVal = -1;
        }
    }
    if( file=="" )
    {   return -10;
    }
    
    filename = file;
    GetMode( mode );
    
    struct stat linkStats;
    if( !stat(filename.c_str(), &linkStats) )
    {   if( writeMode )
        {   //  We're overwriting!
            retVal = 7;
        }
    }
    else
    {   if( readMode )
        {   //  File doesn't exist!
            return -7;
        }
    }

    fileOpen = true;
    delete memShell;
    memShell = new Mshell( memFS );
    if( readMode && readOnOpen )
    {   Read();
        ChDir( dir );
    }
    return retVal;
}

/** @retval -44 No file open.
 */
inline int XMLFile::CloseFile( bool writeOnClose )
{   int retVal(0);
    if( !fileOpen )
    {   return -44;
    }
    if( writeOnClose && writeMode )
    {   retVal = Write();
    }
    readMode = writeMode = false;
    fileOpen = false;
    return retVal;
}

/** @retval -30 Not in write-mode.
 */
inline int XMLFile::Write( void )
{   if( !writeMode ) return -30;
    std::ofstream file( filename.c_str() );
    XMLParser parser( file );
    //  DEBUG NOTE What does the "/" do?
    memShell->Export( parser, "/" );
    return 0;
}

/** @retval -20 Not in read-mode.
 */
inline int XMLFile::Read( void )
{   if( !readMode ) return -20;
    //std::cerr << "XMLFile::" << __FUNCTION__ << " : " << filename.c_str() << std::endl;
    std::ifstream file( filename.c_str() );
    XMLParser parser( file );
    //  DEBUG NOTE What does the "/" do?
    memShell->Import( parser, "/" );
    return 0;
}

inline int XMLFile::ChDir( const std::string &dir )
{   return memShell->ChDir( dir.c_str() );
}

inline int XMLFile::MkDir( const std::string &dir )
{   return memShell->MkDir( dir.c_str() );
}

inline int XMLFile::RmDir( const std::string &dir )
{   return memShell->RmDir( dir.c_str() );
}

inline int XMLFile::EmptyDir( const std::string &dir )
{   int retVal = memShell->RmDir( dir.c_str() );
    retVal += memShell->MkDir( dir.c_str() );
    return retVal;
}

template <class TContent>
inline int XMLFile::SetContent(const std::string &entryName, const TContent &value)
{   return memShell->SetContent( entryName.c_str(), value, true);
}

inline int XMLFile::SetContent(const std::string &entryName, const std::string &str)
{   return memShell->SetContent( entryName.c_str(), str.c_str(), true);
}

template <class TContent>
inline int XMLFile::GetContent(const std::string &entryName, TContent &content)
{   return memShell->GetContent( entryName.c_str(), &content );
}

inline int XMLFile::GetContent(const std::string &entryName, std::string &content)
{   char* ss( NULL );
    int retVal( memShell->GetContent( entryName.c_str(), &ss ) );
    if( !retVal ) content.assign( ss );
    return retVal;
}

inline int XMLFile::GetMode( const char &mode )
{   readMode = writeMode = false;
    if( mode=='r' || mode=='R' )
    {   readMode = true;
        return 0;
    }
    if( mode=='w' || mode=='W' )
    {   writeMode=true;
        return 1;
    }
    return -10;
}

inline int XMLFile::LockFile( void )
{
    int locked = 0;
    if (lockfile) return(1);
    
    lockfile = fopen(filename.c_str(), "r");
    if (!lockfile) return(-1);
    int fd = fileno(lockfile);
    if (fd==-1) return(-1);
    while (!locked)
    {
        locked = !flock(fd,LOCK_EX | LOCK_NB);
        usleep(1000);
    }
    return !locked;
}

inline int XMLFile::UnlockFile( void )
{
    int retval = 0;
    if (!lockfile) return(0);
    int fd = fileno(lockfile);
    if (fd==-1) return(-1);
    retval = !flock(fd, LOCK_UN);
    retval = fclose(lockfile);
    lockfile = NULL;
    return retval;
}

inline int XMLFile::Clear( void )
{
    Mshell sh(memFS);
    sh.RmDir("/");
    return 0;
}

#endif

